package com.java.Utils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * File和IO流工具类
 */
public class FileUtil {
    /**
     * 整个文件系统中查找文件，支持相似查找
     *
     * @param fileName 文件名
     * @return 查到的文件集合
     * @throws Exception 受检异常
     */
    public static List<File> searchFilesInSystem(String fileName) throws Exception {
        File[] listRoots = File.listRoots();
        
        List<File> resultFiles = new ArrayList<>();
        for (File listRoot : listRoots) {
            resultFiles = Stream.concat(resultFiles.stream(), searchFilesInPath(listRoot.getPath(), fileName).stream()).collect(Collectors.toList());
        }
        return resultFiles;
    }
    
    /**
     * 根据文件名在指定路径中相似查找文件，路径必须为文件夹路径
     *
     * @param path     文件夹路径
     * @param fileName 文件名
     * @return 查到的文件集合
     * @throws Exception 受检异常
     */
    public static List<File> searchFilesInPath(String path, String fileName) throws Exception {
        File file = new File(path);
        if (!file.exists() || !file.isDirectory()) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误路径为" + file.getPath());
        }
        
        File[] childFiles = file.listFiles();
        if (childFiles == null || childFiles.length == 0) {
            return null;
        }
        
        List<File> resultFiles = new ArrayList<>();
        for (File childFile : childFiles) {
            if (!childFile.isDirectory()) {
                if (childFile.getName().contains(fileName)) {
                    resultFiles.add(childFile);
                }
                continue;
            }
            
            List<File> resultChildFiles = searchFilesInPath(childFile.getPath(), fileName);
            if (resultChildFiles != null) {
                resultFiles = Stream.concat(resultFiles.stream(), resultChildFiles.stream()).collect(Collectors.toList());
            }
        }
        return resultFiles;
    }
    
    /**
     * 读取所有文件类型的文件内的所有内容
     *
     * @param inputStream 字节输入流
     * @return 文件内容字节集合
     * @throws Exception 受检异常
     */
    public static List<Byte> readAllContentsOfFile(InputStream inputStream) throws Exception {
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
            List<Byte> resultBytes = new ArrayList();
            
            int length = 0;
            byte[] buffer = new byte[8192];
            while ((length = bufferedInputStream.read(buffer)) != -1) {
                Byte[] bytes = new Byte[length];
                for (int i = 0; i < length; i++) {
                    bytes[i] = buffer[i];
                }
                
                resultBytes = Stream.concat(resultBytes.stream(), Arrays.stream(bytes)).collect(Collectors.toList());
            }
            return resultBytes;
        } catch (FileNotFoundException e) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误信息为" + e.getMessage());
        }
    }
    
    /**
     * 读取纯文本类型文件内的所有内容。换行符只适用于Windows
     *
     * @param reader 字符输入流
     * @return 文件内容字符集合
     * @throws Exception 受检异常
     */
    public static StringBuilder readAllContentsOfFile(Reader reader) throws Exception {
        try (BufferedReader bufferedReader = new BufferedReader(reader)) {
            StringBuilder resultChars = new StringBuilder();
            
            String str = "";
            while ((str = bufferedReader.readLine()) != null) {
                resultChars.append(str + "\r\n");
            }
            return resultChars;
        } catch (FileNotFoundException e) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误信息为" + e.getMessage());
        }
    }
    
    /**
     * 读取所有文件类型的文件内的所有内容。换行符只适用于Windows
     *
     * @param inputStream 字节输入流
     * @param encode      字符集
     * @return 文件内容字符集合
     * @throws Exception 受检异常
     */
    public static StringBuilder readAllContentsOfFile(InputStream inputStream, String encode) throws Exception {
        try (
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, encode);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        ) {
            StringBuilder resultChars = new StringBuilder();
            
            String str = "";
            while ((str = bufferedReader.readLine()) != null) {
                resultChars.append(str + "\r\n");
            }
            return resultChars;
        } catch (FileNotFoundException e) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误信息为" + e.getMessage());
        }
    }
    
    /**
     * 根据指定路径删除所有能删除的文件或文件夹，包括本身
     *
     * @param path 文件夹路径
     * @throws Exception 受检异常
     */
    public static void deleteFileOrDir(String path) throws Exception {
        File file = new File(path);
        if (!file.exists()) {
            throw new Exception("路径错误，请检查路径是否存在。错误路径为" + file.getPath());
        }
        
        if (file.isDirectory()) {
            File[] childFiles = file.listFiles();
            for (File childFile : childFiles) {
                if (!childFile.isDirectory()) {
                    childFile.delete();
                    continue;
                }
                
                deleteFileOrDir(childFile.getPath());
            }
        }
        
        file.delete();
    }
    
    /**
     * 拷贝文件或文件夹到指定目录下，包括本身。适用于任何类型文件的拷贝且不会中文乱码
     *
     * @param fromPath 源路径
     * @param toPath   目标路径
     * @throws Exception 受检异常
     */
    public static void copyFileOrDir(String fromPath, String toPath) throws Exception {
        File fromFile = new File(fromPath);
        File toFile = new File(toPath);
        if (!fromFile.exists() || !toFile.exists()) {
            throw new Exception("路径错误，请检查路径是否存在。错误路径为" + fromFile.getPath());
        }
        
        if (fromFile.isDirectory() && toFile.isFile()) {
            throw new Exception("文件夹不能拷贝到文件中，请修改目标路径");
        }
        
        // 文件或文件夹拷贝到文件夹，则先创建该文件或文件夹。若是文件则直接拷贝文件内容，若是文件夹则遍历子文件
        if (toFile.isDirectory()) {
            toPath = toPath + File.separator + fromFile.getName();
            toFile = new File(toPath);
            
            if (fromFile.isDirectory()) {
                toFile.mkdirs();
            } else {
                toFile.createNewFile();
            }
        }
        
        // 文件拷贝到文件，则直接拷贝文件内容
        if (toFile.isFile()) {
            try (
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(fromFile));
                    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(toFile));
            ) {
                byte[] buffer = new byte[8192];
                int length = 0;
                while ((length = bufferedInputStream.read(buffer)) != -1) {
                    bufferedOutputStream.write(buffer, 0, length);
                }
                bufferedOutputStream.flush();
            }
            return;
        }
        
        // 文件夹拷贝到文件夹，则遍历子文件
        File[] listFiles = fromFile.listFiles();
        for (File listFile : listFiles) {
            copyFileOrDir(listFile.getPath(), toPath);
        }
    }
    
    /**
     * 压缩文件或文件夹
     *
     * @param unZipPath 被压缩路径
     * @param zipPath   压缩到路径，必须是文件夹路径
     * @param encode    字符集
     * @throws Exception 受检异常
     */
    public static void zipFileOrDir(String unZipPath, String zipPath, String encode) throws Exception {
        File unZipFile = new File(unZipPath);
        if (!unZipFile.exists()) {
            throw new Exception("路径错误，请检查路径是否存在。错误路径为" + unZipFile.getPath());
        }
        
        File zipRootFile = new File(zipPath);
        if (!zipRootFile.exists() || !zipRootFile.isDirectory()) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误路径为" + zipRootFile.getPath());
        }
        
        String zipName = unZipFile.getName();
        if (zipName.contains(".")) {
            zipName = zipName.substring(0, zipName.indexOf('.'));
        }
        zipName = zipName + ".zip";
        File zipFile = new File(zipPath, zipName);
        
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile), Charset.forName(encode))) {
            zipRecursion(unZipFile, unZipFile.getName(), zipOutputStream);
        }
    }
    
    public static void zipRecursion(File unZipFile, String zipName, ZipOutputStream zipOutputStream) throws IOException {
        if (unZipFile.isFile()) {
            ZipEntry nextEntry = new ZipEntry(zipName);
            zipOutputStream.putNextEntry(nextEntry);
            
            FileInputStream fileInputStream = new FileInputStream(unZipFile);
            int length;
            byte[] buffer = new byte[1024];
            while ((length = fileInputStream.read(buffer)) != -1) {
                zipOutputStream.write(buffer, 0, length);
            }
            return;
        }
        
        File[] listFiles = unZipFile.listFiles();
        for (File listFile : listFiles) {
            zipRecursion(listFile, zipName + File.separator + listFile.getName(), zipOutputStream);
        }
    }
    
    /**
     * 解压
     *
     * @param zipPath   被解压路径
     * @param unZipPath 解压到路径，必须是文件夹路径
     * @param encode    字符集
     * @throws Exception 受检异常
     */
    public static void unzip(String zipPath, String unZipPath, String encode) throws Exception {
        File zipFile = new File(zipPath);
        if (!zipFile.exists() || !zipFile.getName().contains(".zip")) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误路径为" + zipFile.getPath());
        }
        
        File unZipFile = new File(unZipPath);
        if (!unZipFile.exists() || !unZipFile.isDirectory()) {
            throw new Exception("路径错误，请检查路径是否存在或正确。错误路径为" + unZipFile.getPath());
        }
        
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile), Charset.forName(encode))) {
            ZipEntry nextEntry;
            while ((nextEntry = zipInputStream.getNextEntry()) != null) {
                String nextEntryPath = nextEntry.toString();
                int lastSeparatorIndex = nextEntryPath.lastIndexOf(File.separator);
                if (lastSeparatorIndex != -1) {
                    String dirPath = nextEntryPath.substring(0, lastSeparatorIndex);
                    new File(unZipFile, dirPath).mkdirs();
                }
                
                try (FileOutputStream fileOutputStream = new FileOutputStream(new File(unZipFile, nextEntryPath))) {
                    int length = 0;
                    byte[] buffer = new byte[1024];
                    while ((length = zipInputStream.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, length);
                    }
                }
            }
        }
    }
}
